#ifndef _APPLICATION_H
#define _APPLICATION_H

#include <cassert>
#include <cstdlib>
#include <list>
#include <map>
#include <tr1/memory>

#include <gtkmm.h>
#include <gtkglmm.h>

#include "utils.h"
#include "reseatable_reference.h"
#include "glwindow.h"
#include "scenemanager.h"
#include "nodeeditor.h"

class Application
{
	public:
		Application(int argc, char ** argv);
		
		int run();
		bool init();
		void deinit();
		void onFileNew();
		void onFileOpen();
		void onFileSave();
		void onFileSaveAs();
		void onFileExport();
		void onFileRefresh();
		void onCameraChanged();
		
		// these return false if the save was cancelled, but true otherwise (even if there was an error)
		bool fileSave();
		bool fileSaveAs();
		
		void onFileQuit();
		void onHelpAbout();
		bool onWindowClose(GdkEventAny* event);
		void onTreeRowActivated(const Gtk::TreeModel::Path & path, Gtk::TreeViewColumn * model);
		void onTreeRowEdit(SceneNode * node);
		bool onFpsPrintTimeout();
		bool onAssetRefreshTimeout();
		void treeDeselect();
		void onTreeRowDeleted(const Gtk::TreeModel::Path& path);
		void onTreeRowInserted(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter);
		void onTreeRowChanged(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter);
		void onTreeRowsReordered(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter, int* new_order);
		
	private:
		SceneManager scene;
		std::tr1::shared_ptr <SceneNode> copiedNode;
		std::string sceneFile;
		int newNodeCount;
		typedef std::map <const SceneNode *, Gtk::TreeRowReference> rowLookup_type;
		rowLookup_type rowLookup;
		bool changed;
		reseatable_reference <SceneNode> curCameraNode;
		mutable reseatable_reference <const SceneNode> rowDropInsertBeforeNodeIfPossible;
		
		// This is required because we want to override what happens when the right click
		// button is pressed, but we also want the default behavior. gtkmm requires this
		// to be implemented using inheritance.
		class TreeViewHelper : public Gtk::TreeView
		{
			public:
				sigc::signal <bool, GdkEventButton*> composite_signal_button_press_event;
				sigc::signal <void, const Glib::RefPtr< Gdk::DragContext >&> composite_signal_drag_drop;
				
			protected:
				virtual bool on_button_press_event(GdkEventButton * event)
				{
					bool handled = Gtk::TreeView::on_button_press_event(event);
					handled = composite_signal_button_press_event(event) || handled;
					return handled;
				}
				
				virtual bool on_drag_drop(const Glib::RefPtr< Gdk::DragContext >& context, int x, int y, guint time)
				{
					composite_signal_drag_drop(context);
					return Gtk::TreeView::on_drag_drop(context, x, y, time);
				}
		};
		
		// We want to control which rows we can drag and drop
		class TreeStoreHelper : public Gtk::TreeStore
		{
			public:
				TreeStoreHelper(const Gtk::TreeModelColumnRecord& columns) : Gtk::TreeStore(columns) {}
				sigc::signal <bool, const Gtk::TreeModel::Path&> signal_row_draggable;
				sigc::signal <bool, const Gtk::TreeModel::Path&, const Gtk::SelectionData&> signal_row_drop_possible;
				
			protected:
				virtual bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const
				{
					return signal_row_draggable(path);
				}
				
				virtual bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& selection_data) const
				{
					return signal_row_drop_possible(dest, selection_data);
				}
		};
		
		Gtk::Window mainWindow;
		reseatable_reference <TreeViewHelper> tree;
		reseatable_reference <GLWindow> drawarea;
		reseatable_reference <Gtk::TextBuffer> outputBuffer;
		reseatable_reference <Gtk::ScrolledWindow> outputWindow;
		reseatable_reference <Gtk::TextBuffer> glLogBuffer;
		reseatable_reference <Gtk::ScrolledWindow> glLogWindow;
		reseatable_reference <Gtk::ComboBoxText> cameraDropdown;
		Glib::RefPtr <Gtk::ActionGroup> refActionGroup;
		Glib::RefPtr <Gtk::UIManager> uiManager;
		Glib::RefPtr <TreeStoreHelper> nodeStore;
		std::tr1::shared_ptr <Gtk::Menu> popupMenu;
		Glib::RefPtr <Gtk::TreeSelection> treeSelection;
		Gtk::Label fpsCounter;
		
		int mouseButtonDown;
		MATHVECTOR <double, 2> mouseDownLocation;
		
		// I don't see any better way to find out where we've dragged and dropped
		// after it happens than to cache these paths off in the rowDropPossible
		// function and use them in onTreeDragFinished
		mutable Gtk::TreeModel::Path treeDragSource;
		mutable Gtk::TreeModel::Path treeDragDest;
		
		typedef std::map <std::string, Glib::RefPtr<Gdk::Pixbuf> > iconMap_type;
		iconMap_type iconMap;
		
		class NodeModel : public Gtk::TreeModelColumnRecord
		{
			public:
				NodeModel()
				{ add(icon); add(name); add(ptr); }
				
				Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > icon;
				Gtk::TreeModelColumn<Glib::ustring> name;
				Gtk::TreeModelColumn<SceneNode*> ptr;
		} nodeModel;
		
		void writeOutput(std::string text);
		void writeError(std::string text) {writeOutput(std::string("ERROR: ") + text);}
		void writeGlLog(std::string text);
		void clearGlLog();
		
		bool checkErrorCritical(const std::string & text);
		
		Gtk::TreeModel::Row getRowForNode(const SceneNode * node);
		const SceneNode * getNodeForRowConst(const Gtk::TreeModel::Row & row) const;
		SceneNode * getNodeForRow(const Gtk::TreeModel::Row & row)
		{
			return const_cast<SceneNode*>(getNodeForRowConst(row));
		}
		
		// returns NULL if nothing is selected
		SceneNode * getSelectedNode();
		
		// if parent is NULL, the node is added to the scene root
		// returns the permanent location of the scene node
		// or NULL if the node couldn't be added (for example, if
		// parent doesn't support it)
		SceneNode * addNode(const SceneNode & node, SceneNode * parent = NULL, const SceneNode * beforeNodeIfPossible = NULL);
		
		// deletes the node in the scene that matches the memory address of the node
		// reference that was passed in
		// returns true if something was deleted
		bool deleteNode(const SceneNode & node);
		
		bool onTreeButtonPress(GdkEventButton * event);
		
		bool onDrawAreaButtonPress(GdkEventButton * event);
		bool onDrawAreaButtonRelease(GdkEventButton * event);
		bool onDrawAreaScrollEvent(GdkEventScroll* event);
		bool onDrawAreaMotionEvent(GdkEventMotion* event);
		
		void addNodeAtSelected(const std::string & type);
		
		void cutSelectedNode();
		void copySelectedNode();
		void pasteSelectedNode();
		void deleteSelectedNode();
		void refreshSelectedNode();
		void resetSelectedNode();
		
		void onTreeDragFinish(const Glib::RefPtr<Gdk::DragContext>& context/*, int, int, unsigned int*/);
		bool rowDraggable(const Gtk::TreeModel::Path& path) const;
		bool rowDropPossible(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& selection_data) const;
		
		// puts up a dialog asking the user if they want to save or not
		// also offers them an option to cancel the current operation
		// returns true if the user wants to cancel, otherwise returns false
		bool checkForSave();
		
		void sendSceneToRenderer();
		void onSceneChanged();
		void handleNodeRename(const SceneNode & node, const std::string & oldname);
};

#endif
